﻿using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Globalization;
using System.Data;
using System.Data.Linq;
using System.Data.SqlClient;
using System.Linq.Dynamic;
using System.Linq;
using System.Text;
using IntraVision.Web.Toolkit;

namespace IntraVision.Web
{
    public class EntityTableController<TEntity, TDataContext> : EntityListController<TEntity, TDataContext>
        where TEntity : class, new()
        where TDataContext : DataContext, new()
    {
        protected List<Column> _Columns;
        protected List<IFilterCondition> _FilterConditions;

        public List<IFilterCondition> FilterConditions
        {
            get { return _FilterConditions; }
        }

        protected int _Count = 0;
        protected int _Page = 0;
        protected int _PageSize = 0;
        protected int _PageCount = 1;

        protected bool _AllowSaveFilter = false;

        protected string _DefaultColumns;
        protected string _DefaultOrderBy;


        public event EntityControllerEvent CreateFilter;
        public event EntityControllerEvent FilterInitialized;

        public EntityTableController()
            : base()
        {
            _FilterConditions = new List<IFilterCondition>();
            _Columns = new List<Column>();
        }

        public override void List()
        {
            if (string.IsNullOrEmpty(TemplatePath)) TemplatePath = "~/views/" + Name + "/list.haml";
            if (string.IsNullOrEmpty(LayoutPath) && string.IsNullOrEmpty(Request["nolayout"])) LayoutPath = "~/views/layouts/page.haml";

            CreateColumns();

            using (var dc = new TDataContext())
            {
                BeforeLoadList(dc);

                if (!string.IsNullOrEmpty(Request["json"]))
                {
                    IQueryable<TEntity> list = dc.GetTable<TEntity>();
                    if (!string.IsNullOrEmpty(Request["where"]))
                        list = list.Where(Request["where"]);
                    if (!string.IsNullOrEmpty(Request["search"]))
                        list = list.Where(LINQHelper.GenerateSearchString(typeof(TEntity)), Request["search"]);
                    if (!string.IsNullOrEmpty(Request["orderby"]))
                        list = list.OrderBy(Request["orderby"]);
                    TEntity[] entities = list.ToArray();
                    RenderJSON(entities);
                }
                else
                {
                    IQueryable<TEntity> list = dc.GetTable<TEntity>() as IQueryable<TEntity>;
                    
                    list = Filter(dc, list);
                    list = CustomFilter(dc, list);
                    list = Search(list);
                    list = Order(list);
                    list = Paginate(list);
                    //ViewData["data"] = DataBinder.SourceToTable(PreparePage(dc, list));

                    InitColumns();

                    ViewData["entities"] = PreparePage(dc,list);

                    //Highlight last changed record 
                    if (Session[Name + "_changed"] != null)
                    {
                        ViewData["changedId"] = Session[Name + "_changed"];
                        Session[Name + "_changed"] = null;
                    }

                    RegisterScript("table.js", "jquery.validate.pack.js", "jquery.validate.additional.js", "jquery.multipleselect.js");
                    if (Language.ToUpper() != "EN") RegisterScript(string.Format("jquery.validate.messages-{0}.js", Language));
                    RenderView();

                    if (dc.Connection.State != ConnectionState.Closed) dc.Connection.Close();
                }
            }
        }

        public virtual void LoadFilter()
        {
            if (string.IsNullOrEmpty(TemplatePath)) TemplatePath = "~/views/_filter.haml";
            if (string.IsNullOrEmpty(LayoutPath)) LayoutPath = "none";

            CreateColumns();

            using (var dc = new TDataContext())
            {
                BeforeLoadList(dc);

                if (CreateFilter != null)
                {
                    ViewData["hasfilter"] = true;

                    //Create filter conditions
                    CreateFilter(dc, null);

                    //NameValueCollection, from which filter will be initialized.
                    NameValueCollection filterData = System.Web.HttpUtility.ParseQueryString(Convert.ToString(Session[Name + "_filter"], CultureInfo.InvariantCulture));

                    //Initialize filter either from filterdata
                    InitFilter(filterData);

                    if (FilterInitialized != null) FilterInitialized(null, dc);

                    ViewData["filterconditions"] = FilterConditions;
                }

                InitColumns();
                ViewData["orderby"] = Session[Name + "_orderby"];
                RenderView();

                if (dc.Connection.State != ConnectionState.Closed) dc.Connection.Close();
            }
        }

        protected virtual void CreateColumns() { }
        
        protected virtual void InitColumns()
        {
            if (_Columns.Count == 0) return;
            
            string key = Name + "_visiblecolumns";

            if (!string.IsNullOrEmpty(Request["clearfilter"]))
                Session[key] = _DefaultColumns;
            else if (!string.IsNullOrEmpty(Request["visiblecolumns"]))
            {
                ViewData["filterapplied"] = true;
                Session[key] = Request["visiblecolumns"];
            }
            if (Session[key] == null) Session[key] = _DefaultColumns;

            string[] vcs = Session[key].ToString().ToLower().Split(',');
            int order = 1;
            foreach (string vc in vcs)
            {
                var col = _Columns.SingleOrDefault(c => c.Id.ToLower() == vc);
                if (col != null)
                {
                    col.Order = order++;
                    col.Visible = true;
                }
            }

            string[] ocs = ((string)Session[Name + "_orderby"]).ToLower().Split(',');
            order = 1;
            foreach (string oc in ocs)
            {
                bool desc = oc.EndsWith("desc");
                string cn = oc;
                if (oc.EndsWith(" desc")) cn = oc.Substring(0, oc.Length - 5);
                if (oc.EndsWith(" asc")) cn = oc.Substring(0, oc.Length - 4);

                var col = _Columns.SingleOrDefault(c => c.Id.ToLower() == cn);
                if (col != null)
                {
                    col.SortOrder = order++;
                    col.SortDirection = desc ? SortDirection.Descending : SortDirection.Ascending;
                }
            }

            ViewData["columns"] = _Columns;
        }

        protected virtual IQueryable<TEntity> Search(IQueryable<TEntity> list)
        {
            ViewData["search"] = Request["search"];
            if (!string.IsNullOrEmpty(Request["search"]))
                list = list.Where(LINQHelper.GenerateSearchString(typeof(TEntity)), Request["search"]);
            return list;
        }

        private IQueryable<TEntity> Filter(TDataContext dc, IQueryable<TEntity> list)
        {
            if (CreateFilter != null)
            {
                ViewData["hasfilter"] = true;

                //Create filter conditions
                CreateFilter(dc, null);

                //Init filter

                int defId = 0;
                //Load saved filters from database
                if (_AllowSaveFilter)
                {
                    ViewData["allowsavefilter"] = true;

                    //Save filter if needed
                    if (!string.IsNullOrEmpty(Request["savefilter"]))
                        SaveFilter(dc);

                    LoadFilters(dc, ref defId);
                }

                //Current saved filter
                int filterid = 0;

                //1. Filter has been saved
                if (!string.IsNullOrEmpty(Request["savefilter"]) && Session[Name + "_filterid"] != null)
                {
                    Session[Name + "_filter"] = null;
                    filterid = (int)Session[Name + "_filterid"];
                }
                //2. Or filter has been cleared
                else if (!string.IsNullOrEmpty(Request["clearfilter"]))
                {
                    Session[Name + "_filterid"] = 0;
                    Session[Name + "_filter"] = null;
                }
                //3. Request has filterid
                else if (!string.IsNullOrEmpty(Request["filterid"]) && Int32.TryParse(Request["filterid"], out filterid))
                {
                    Session[Name + "_filterid"] = filterid;
                    Session[Name + "_filter"] = null;
                }
                //4. Request has flt* or fld*
                else if (Request.Params.AllKeys.Any(k => (k.StartsWith("flt") || k.StartsWith("ftt")) && k != "fltorderby"))
                {
                    Session[Name + "_filterid"] = 0;
                    Session[Name + "_filter"] = SerializeFilter();
                }
                //5. Session["filterid"] not null
                else if (Session[Name + "_filterid"] != null && Int32.TryParse(Session[Name + "_filterid"].ToString(), out filterid) && filterid > 0)
                {
                    Session[Name + "_filter"] = null;
                    filterid = (int)Session[Name + "_filterid"];
                }
                //6. Apply default filter
                else if (Session[Name + "_filter"] == null)
                {
                    filterid = defId;
                    Session[Name + "_filterid"] = defId;
                    Session[Name + "_filter"] = null;
                }

                //NameValueCollection, from which filter will be initialized.
                NameValueCollection filterData = System.Web.HttpUtility.ParseQueryString(Convert.ToString(Session[Name + "_filter"], CultureInfo.InvariantCulture));
                //If filterid is set - then load filter.
                if (filterid > 0) filterData = LoadFilter(filterid, dc);

                //Initialize filter either from filterdata
                InitFilter(filterData);

                if (FilterInitialized != null) FilterInitialized(null, dc);
                list = Filter(list);

                ViewData["filterconditions"] = FilterConditions;
            }
            return list;
        }

        protected virtual IQueryable<TEntity> Order(IQueryable<TEntity> list)
        {
            string key = Name + "_orderby";
            if (!string.IsNullOrEmpty(Request["clearfilter"]))
                Session[key] = _DefaultOrderBy;
            else if (!string.IsNullOrEmpty(Request["fltorderby"]))
                Session[key] = Request["fltorderby"];
            if (Session[key] == null)
                Session[key] = _DefaultOrderBy;

            string orderby = Convert.ToString(Session[Name + "_orderby"], CultureInfo.InvariantCulture);
            ViewData["orderby"] = orderby;

            list = (!string.IsNullOrEmpty(orderby)) ? list.OrderBy(orderby) : list;
            return list;
        }

        protected virtual IQueryable<TEntity> Paginate(IQueryable<TEntity> list)
        {
            //Считаем текущую страницу из сессии
            if (Session[Name + "_page"] != null) _Page = (int)Session[Name + "_page"];
            if (Session[Name + "_pagesize"] != null) _PageSize = (int)Session[Name + "pagesize"];

            if (!string.IsNullOrEmpty(Request["page"])) { Int32.TryParse(Request["page"], out _Page); Session[Name + "_page"] = _Page; }
            if (!string.IsNullOrEmpty(Request["pagesize"])) { Int32.TryParse(Request["pagesize"], out _PageSize); Session[Name + "_pagesize"] = _PageSize; }

            _Count = list.Count();
            if (_Count > _PageSize) _PageCount = (int)Math.Ceiling((double)_Count / _PageSize);
            if (_Page > _PageCount || _Page <= 0) _Page = 1;

            ViewData["page"] = _Page;
            ViewData["pagesize"] = _PageSize;
            ViewData["pagecount"] = _PageCount;
            ViewData["count"] = _Count;

            if (_PageSize > 0) list = list.Skip((_Page - 1) * _PageSize).Take(_PageSize);
            return list;
        }

        protected virtual void InitFilter(NameValueCollection data)
        {
            foreach (var fc in _FilterConditions)
                fc.Init(data);
        }

        protected virtual IQueryable<TEntity> Filter(IQueryable<TEntity> list)
        {
            foreach (var fc in _FilterConditions)
            {
                try
                {
                    var newlist = ((IFilterCondition<TEntity>)fc).Filter(list);
                    if (list != newlist) ViewData["filterapplied"] = true;
                    list = newlist;
                }
                catch (Exception ex)
                {
                    ViewData.Exceptions.Add(ex);
                }
            }
            return list;
        }

        protected virtual NameValueCollection LoadFilter(int filterid, TDataContext dc)
        {
            NameValueCollection filterData = Request.Params;
            try
            {
                if (dc.Connection.State != ConnectionState.Open)
                    dc.Connection.Open();

                var user = (ViewData["user"] as IUser);
                SqlCommand cmd = new SqlCommand("SELECT Filter FROM Filter WHERE Id = @Id AND Page = @Page AND (UserId IS NULL OR UserId = @UserId)", (SqlConnection)dc.Connection);

                cmd.Parameters.AddWithValue("@Page", Name);
                cmd.Parameters.AddWithValue("@UserId", (user == null) ? 0 : user.Id);
                cmd.Parameters.AddWithValue("@Id", filterid);

                string filter = Convert.ToString(cmd.ExecuteScalar(), CultureInfo.InvariantCulture);
                if (!string.IsNullOrEmpty(filter))
                {
                    ViewData["filterid"] = filterid;
                    Session[Name + "_filter"] = filter;
                    filterData = System.Web.HttpUtility.ParseQueryString(filter);
                }
            }
            catch (SqlException ex)
            {
                ViewData.Exceptions.Add(ex);
            }
            return filterData;
        }

        protected virtual void LoadFilters(TDataContext dc, ref int fid)
        {
            try
            {
                if (dc.Connection.State != ConnectionState.Open)
                    dc.Connection.Open();

                var user = (ViewData["user"] as IUser);
                SqlCommand cmd = new SqlCommand("SELECT Id,Name,UserId,IsDefault FROM Filter WHERE Page = @Page AND (UserId IS NULL OR UserId = @UserId) ORDER BY UserId DESC, Name", (SqlConnection)dc.Connection);

                cmd.Parameters.AddWithValue("@Page", Name);
                cmd.Parameters.AddWithValue("@UserId", (user == null) ? 0 : user.Id);

                var myfilters = new NameValueCollection();
                var commonfilters = new NameValueCollection();
                var allfilters = new NameValueCollection();
                using (SqlDataReader dr = cmd.ExecuteReader())
                {
                    while (dr.Read())
                    {
                        if (fid == 0 && dr[2] != DBNull.Value && (bool)dr[3]) fid = (int)dr[0];
                        if (dr[2] == DBNull.Value)
                            commonfilters[dr[0].ToString()] = dr[1].ToString();
                        else
                            myfilters[dr[0].ToString()] = dr[1].ToString() + ((bool)dr[3] ? "{def}" : "");
                        allfilters[dr[0].ToString()] = dr[1].ToString() + ((dr[2] != DBNull.Value) ? "" : "(общий)");
                    }
                }

                ViewData["myfilters"] = myfilters;
                ViewData["commonfilters"] = commonfilters;
                ViewData["allfilters"] = allfilters;
            }
            catch (SqlException ex)
            {
                ViewData.Exceptions.Add(ex);
            }
        }

        protected virtual void SaveFilter(TDataContext dc)
        {
            var user = (ViewData["user"] as IUser);
            if (user == null) return;
            try
            {
                if (dc.Connection.State != ConnectionState.Open)
                    dc.Connection.Open();

                int oldFilterId = 0;
                string filterName = Request["filtername"];

                if (string.IsNullOrEmpty(filterName)) throw new ArgumentException("Поле \"Название фильтра\" не заполнено.");
                if (!string.IsNullOrEmpty(Request["oldfilterid"])) Int32.TryParse(Request["oldfilterid"], out oldFilterId);

                string filter = Convert.ToString(Session[Name + "_filter"], CultureInfo.InvariantCulture);

                SqlCommand cmd = new SqlCommand("", (SqlConnection)dc.Connection);
                cmd.Parameters.AddWithValue("@Page", Name);
                if (string.IsNullOrEmpty(Request["commonfilter"]))
                {
                    cmd.Parameters.AddWithValue("@IsDefault", !string.IsNullOrEmpty(Request["isdefault"]));
                    cmd.Parameters.AddWithValue("@UserId", user.Id);
                }
                else
                {
                    cmd.Parameters.AddWithValue("@IsDefault", false);
                    cmd.Parameters.AddWithValue("@UserId", DBNull.Value);
                }

                cmd.Parameters.AddWithValue("@Name", filterName);
                cmd.Parameters.AddWithValue("@Filter", filter);

                if (oldFilterId == 0)
                    cmd.CommandText = "INSERT INTO Filter (Name, UserId, Page, Filter, IsDefault) VALUES(@Name,@UserId,@Page,@Filter,@IsDefault) SELECT SCOPE_IDENTITY()";
                else
                {
                    cmd.CommandText = "UPDATE Filter SET Name=@Name, Filter=@Filter,IsDefault=@IsDefault WHERE Id=@Id AND Page=@Page SELECT @Id";
                    cmd.Parameters.AddWithValue("@Id", oldFilterId);
                }

                int filterId = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture);
                Session[Name + "_filterid"] = filterId;
            }
            catch (Exception ex)
            {
                Session[Name + "_filterid"] = null;
                ViewData.Exceptions.Add(ex);
            }
        }

        private string SerializeFilter()
        {
            StringBuilder sb = new StringBuilder();
            foreach (var key in Request.Params.AllKeys)
            {
                if (!key.StartsWith("flt") && !key.StartsWith("ftt")) continue;

                if (sb.Length > 0) sb.Append("&");
                sb.AppendFormat(CultureInfo.InvariantCulture, "{0}={1}", key, System.Web.HttpUtility.UrlEncode(Request[key]));
            }
            string filter = sb.ToString();
            return filter;
        }

        protected virtual IQueryable<TEntity> PreparePage(TDataContext dc,IQueryable<TEntity> list)
        {
            return list;
        }

        public void MakeDefaultFilter(string id)
        {
            int filterid = 0;
            if (!Int32.TryParse(id, out filterid))
            {
                RenderJSON(new AJAXResult(new ArgumentException("Неправильный идентификатор фильтра.")));
                return;
            }

            using (var dc = new TDataContext())
            {
                try
                {
                    var user = ViewData["user"] as IUser;

                    dc.Connection.Open();
                    SqlCommand cmd = new SqlCommand("UPDATE Filter SET IsDefault = @IsDefault WHERE Id = @Id AND Page = @Page AND UserId = @UserId", (dc.Connection as SqlConnection));
                    cmd.Parameters.AddWithValue("@Id", filterid);
                    cmd.Parameters.AddWithValue("@Page", Name);
                    cmd.Parameters.AddWithValue("@UserId", user.Id);
                    cmd.Parameters.AddWithValue("@IsDefault", (Request["isdefault"].ToLower() == "true"));
                    cmd.ExecuteNonQuery();

                    var result = new AJAXResult(true);
                    result.Id = id;

                    RenderJSON(result);
                }
                catch (SqlException ex)
                {
                    RenderJSON(new AJAXResult(ex));
                }
                finally
                {
                    if (dc.Connection.State != ConnectionState.Closed) dc.Connection.Close();
                }
            }
        }

        public void DeleteFilter(string id)
        {
            int filterid = 0;
            if (!Int32.TryParse(id, out filterid))
            {
                RenderJSON(new AJAXResult(new ArgumentException("Неправильный идентификатор фильтра.")));
                return;
            }

            using (var dc = new TDataContext())
            {
                try
                {
                    dc.Connection.Open();
                    SqlCommand cmd = new SqlCommand("DELETE FROM Filter WHERE Id = @Id AND Page = @Page", (dc.Connection as SqlConnection));
                    cmd.Parameters.AddWithValue("@Id", filterid);
                    cmd.Parameters.AddWithValue("@Page", Name);

                    cmd.ExecuteNonQuery();

                    var result = new AJAXResult(true);
                    result.Id = id;

                    RenderJSON(result);
                }
                catch (SqlException ex)
                {
                    RenderJSON(new AJAXResult(ex));
                }
                finally
                {
                    if( dc.Connection.State != ConnectionState.Closed ) dc.Connection.Close();
                }
            }
        }
    }
}
